﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Epi;
using Epi.Data;
using CDC.SampleShell.Infrastructure;

namespace CDC.SampleShell.Infrastructure
{
    /// <summary>
    /// An example Microsoft Access based record repository for use specifically alongside Epi Info 7.
    /// </summary>
    /// <remarks>
    /// Possible improvements are: Loading data where the column count > 200 (currently this is unimplemented
    /// and will throw an exception), better FindOne() implementation by leveraging ISpecification (currently 
    /// FindOne() allows only a Guid to get the single record), better code contracting, and having a single
    /// repository for each Form instead of a single repository for the whole project.
    /// </remarks>
    public abstract class EntityRepositoryBase<T> : IRepository<IEntity> 
        where T : Entity
    {
        #region Members
        protected readonly Project _project;
        protected readonly IDbDriver _database;
        #endregion // Members

        #region Constructors
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="project">The Epi Info 7 project to attach</param>
        public EntityRepositoryBase(Project project)
        {
            // pre
            Contract.Requires(project != null);
            
            // post
            Contract.Ensures(_project != null);
            Contract.Ensures(_database != null);
            Contract.Ensures(UnitOfWork != null);

            _project = project;
            _database = project.CollectedData.GetDatabase();
            UnitOfWork = new CDC.SampleShell.Infrastructure.UnitOfWork<T>(_project);
        }
        #endregion // Constructors

        #region Properties
        public IUnitOfWork<IEntity> UnitOfWork { get; private set; }
        #endregion // Properties

        #region Methods
        public virtual IEntity CreateNew()
        {
            throw new NotImplementedException();
        }

        protected virtual IEntity ProcessRecord(View form, DataRow row)
        {
            // pre
            Contract.Requires(form != null);
            Contract.Requires(row != null);

            // post
            Contract.Ensures(Contract.Result<IEntity>() != null);

            string globalRecordIdString = row["t.GlobalRecordId"].ToString();

            Contract.Assert(globalRecordIdString.Length == 36);

            var globalRecordId = new Guid(globalRecordIdString);
            //var record = new T(globalRecordId, form);
            object[] args = new object[2];
            args[0] = globalRecordId;
            args[1] = form;
            var record = (T)Activator.CreateInstance(typeof(T), args);

            record.LastSaveLogonName = row["LastSaveLogonName"].ToString();

            if (row.Table.Columns.Contains("FKEY"))
            {
                record.ForeignKey = row["FKEY"].ToString();
            }
            else if (row.Table.Columns.Contains("t.FKEY"))
            {
                record.ForeignKey = row["t.FKEY"].ToString();
            }
            else
            {
                record.ForeignKey = String.Empty;
            }

            if (row["LastSaveTime"] != DBNull.Value)
            {
                record.LastSaveTime = Convert.ToDateTime(row["LastSaveTime"]);
            }

            foreach (DataColumn dc in row.Table.Columns)
            {
                if (dc.ColumnName.Equals("t.GlobalRecordId", StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                record.SetData(dc.ColumnName, row[dc.ColumnName]);
            }

            return record;
        }

        /// <summary>
        /// Retrieves a single record
        /// </summary>
        /// <param name="form">The form to which the record belongs</param>
        /// <param name="id">The global record id of the record</param>
        /// <returns>Entity</returns>
        public virtual IEntity FindOne(View form, Guid id)
        {
            // For small forms we can do a SELECT * FROM all tables
            if (form.Fields.DataFields.Count < 190)
            {
                // TODO: Example of how to create a query using Epi Info 7's IDbDriver
                Query selectQuery = _database.CreateQuery("SELECT * " + form.FromViewSQL + " WHERE RecStatus > 0 AND t.GlobalRecordId = @GlobalRecordId");
                selectQuery.Parameters.Add(new QueryParameter("@GlobalRecordId", DbType.String, id.ToString()));
                DataTable table = _database.Select(selectQuery);

                // Maybe there are better ways to handle this
                if (table.Rows.Count != 1)
                {
                    throw new InvalidOperationException();
                }

                var row = table.Rows[0];

                return ProcessRecord(form, row);
            }
            // For larger forms we can't do this due to MDB 255 column limit, so select across page tables instead and build the object from those tables
            else
            {
                Query selectQuery = _database.CreateQuery("SELECT * FROM [" + form.TableName + "] WHERE RecStatus > 0");
                DataTable baseTable = _database.Select(selectQuery);

                throw new NotImplementedException();
            }
        }

        public virtual IEnumerable<IEntity> FindRecords(View form, ISpecification criteria)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Retrieves all records from the database for a given form
        /// </summary>
        /// <param name="form">The form to which the records belong</param>
        /// <returns>IEnumerable</returns>
        public virtual IEnumerable<IEntity> FetchAll(View form)
        {
            if (form.Fields.DataFields.Count < 190)
            {
                Query selectQuery = _database.CreateQuery("SELECT * " + form.FromViewSQL + " WHERE RecStatus > 0");
                DataTable fullTable = _database.Select(selectQuery);

                var records = new List<IEntity>();

                foreach (DataRow row in fullTable.Rows)
                {
                    records.Add(ProcessRecord(form, row));
                }

                return records.AsEnumerable();
            }
            else
            {
                Query selectQuery = _database.CreateQuery("SELECT * FROM [" + form.TableName + "] WHERE RecStatus > 0");
                DataTable baseTable = _database.Select(selectQuery);

                throw new NotImplementedException();
            }
        }

        #endregion // Methods
    }
}
